package distributedlock.lock;

import distributedlock.utils.Snowflake;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁实现
 *
 * @author zhanghui145
 * @date 2022/2/11 14:42
 * @since 1.0.0
 */
@Component
public class RedisLock {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private ValueOperations<String, String> valueOperations;
    private String lockName;
    private final Snowflake snowflake = new Snowflake(1, 1, 1609430400000L);

    @PostConstruct
    public void init() {
        this.valueOperations = this.redisTemplate.opsForValue();
    }

    /**
     * 默认获取锁的时间为2秒
     */
    public void lock() {
        this.lock(2);
    }

    /**
     * 比较简单粗暴，直接用的do-while循环
     * 尝试获取锁，如果获取失败则抛出异常。也可以不抛出异常，返回值修改为true/false
     *
     * @param expireTime 过期时间
     */
    public void lock(int expireTime) {
        setLockTime(expireTime);
        do {
            Boolean suc = valueOperations.setIfAbsent("RedisLockStart", "0", 1, TimeUnit.SECONDS);
            if (suc != null && suc) {
                System.out.println(lockName + "抢夺锁成功");
                return;
            }
        } while (exists());
        throw new RuntimeException("redisLock time out, not lock");
    }

    /**
     * 在尝试获取锁之前，先设置一个tryLock时间，在这个时间可以获取锁
     *
     * @param expire 尝试获取锁过期时间
     */
    private void setLockTime(int expire) {
        this.lockName = "lock:" + snowflake.nextId();
        valueOperations.set(this.lockName, "lockTryTime", expire, TimeUnit.SECONDS);
    }

    /**
     * 释放锁
     */
    public void unlock() {
        System.out.println(this.lockName + "释放锁");
        redisTemplate.delete(this.lockName);
    }

    public String getLockName() {
        return this.lockName;
    }

    private boolean exists() {
        return valueOperations.get(lockName) != null;
    }
}
